package com.yangxk.upload.starter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import javax.servlet.ServletContext;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;

/**
 * 采用disk存储时配置资源文件映射
 *
 * @author yangxk
 * @date 2019/2/23 14:10
 */
public class ResourceMapping implements ApplicationListener, ServletContextAware {

    private Logger logger = LoggerFactory.getLogger(ResourceMapping.class);

    private ServletContext servletContext;

    private UploadProperties uploadProperties;

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        if (applicationEvent instanceof ContextRefreshedEvent) {
            try {
                String prefix = uploadProperties.getDisk().getResourceMappingPrefix();
                String urlPattern = "/" + prefix + "/**";

                // locations的最后需要添加 / 否则将不能正确映射,
                // 因为new java.net.URL((URL context, String spec) 获取相对路径时
                // (c:/users/admin/upload, a.jpg) --> c:/users/admin/a.jpg
                // (c:/users/admin/upload/, a.jpg) --> c:/users/admin/upload/a.jpg
                String locations = "file:" + uploadProperties.getDisk().getDir() + File.separator + prefix + File.separator;

                WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
                // 获取spring容器中的资源映射类
                SimpleUrlHandlerMapping resourceHandlerMapping = (SimpleUrlHandlerMapping)context.getBean("resourceHandlerMapping");
                if (resourceHandlerMapping == null) {
                    return ;
                }

                // 获取已经配置好的资源映射
                Map<String, HttpRequestHandler> urlMap = (Map<String, HttpRequestHandler>) resourceHandlerMapping.getUrlMap();
                if (urlMap == null)
                    return ;

                // 获取 /** 对应的资源映射handler
                ResourceHttpRequestHandler handler = (ResourceHttpRequestHandler)urlMap.get("/**");

                // 创建disk对应的资源映射handler
                ResourceHttpRequestHandler newHandler = new ResourceHttpRequestHandler();
                newHandler.setUrlPathHelper(handler.getUrlPathHelper());
                newHandler.setContentNegotiationManager(handler.getContentNegotiationManager());
                newHandler.setApplicationContext(context);
                newHandler.setServletContext(servletContext);
                newHandler.setLocationValues(Arrays.asList(locations));
                newHandler.afterPropertiesSet();

                // 将disk的handler加入map中
                urlMap.put(urlPattern, newHandler);

                // 通过反射调用registerHandlers方法, 将其添加到spring Mvc的资源映射处理器中
                Class<? extends SimpleUrlHandlerMapping> hmClass = resourceHandlerMapping.getClass();
                Method method = hmClass.getDeclaredMethod("registerHandlers", Map.class);
                method.setAccessible(true);
                method.invoke(resourceHandlerMapping, urlMap);

                logger.info("文件上传包中的资源文件映射配置成功: {} --> {}", urlPattern, locations);
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("文件上传包中的配置资源文件映射失败: {}", e);
            }
        }
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    public void setUploadProperties(UploadProperties uploadProperties) {
        this.uploadProperties = uploadProperties;
    }
}
